﻿/**************************************************************************\
    Copyright Microsoft Corporation. All Rights Reserved.
\**************************************************************************/

namespace C4F.DevKit.Contacts
{
    using C4F.DevKit.Contacts.Interop;
    using C4F.DevKit.Contacts.Standard;
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.ComponentModel;

    public struct Person : IEquatable<Person>
    {
        private const string EmailToken = "/EMAIL:";
        private const string PhoneToken = "/PHONE:";
        private const string PreferredLabelToken = "/PREFLABEL:";

        #region Fields
        private readonly string _name;
        private readonly string _id;
        private readonly string _phone;
        private readonly string _email;
        private readonly string _preferredLabel;
        private readonly Contact _contact;
        #endregion

        #region Internal Methods shared with PersonBuilder

        private static string _BuildPersonId(Dictionary<string, string> tokens)
        {
            Assert.IsNotNull(tokens);
            if (tokens.Count == 0)
            {
                return null;
            }

            const string tokenFormat = "{0}\"{1}\" ";
            StringBuilder sb = new StringBuilder();
            foreach(KeyValuePair<string, string> pair in tokens)
            {
                Assert.IsTrue(pair.Key.StartsWith("/"));
                sb.AppendFormat(tokenFormat, pair.Key, pair.Value);
            }
            sb.Remove(sb.Length - 1, 1);
            return sb.ToString();
        }

        internal static string GenerateId(Contact contact, string phone, string email, string preferredLabel)
        {
            const string tokenFormat = "{0}\"{1}\" ";

            StringBuilder sb = new StringBuilder();
            if (null != contact)
            {
                sb.Append(contact.Id).Append(' ');
            }
            if (null != phone)
            {
                sb.AppendFormat(tokenFormat, PhoneToken, phone);
            }
            if (null != email)
            {
                sb.AppendFormat(tokenFormat, EmailToken, email);
            }
            if (null != preferredLabel)
            {
                sb.AppendFormat(tokenFormat, PreferredLabelToken, preferredLabel);
            }
            if (sb.Length > 0)
            {
                Assert.AreEqual(sb.ToString(sb.Length - 1, 1), " ");
                sb.Remove(sb.Length - 1, 1);
            }
            return sb.ToString();
        }

        internal static void ParseId(string id, out string phone, out string email, out string preferredLabel)
        {
            phone = null;
            email = null;
            preferredLabel = null;

            id = (id ?? "").Trim();
            if (id.Length != 0)
            {
                Dictionary<string, string> tokens = ContactUtil.TokenizeId(id);
                tokens.TryGetValue(EmailToken, out email);
                tokens.TryGetValue(PhoneToken, out phone);
                tokens.TryGetValue(PreferredLabelToken, out preferredLabel);
            }
        }

        internal static string GetCoercedName(string name, Contact contact, string preferredLabel)
        {
            string ret = name;
            if (null == ret && null != contact)
            {
                ret = contact.Names[preferredLabel].FormattedName;
                if (null == ret)
                {
                    ret = contact.Names.Default.FormattedName;
                }
            }
            return ret ?? string.Empty;
        }

        internal static string GetCoercedEmail(string email, Contact contact, string preferredLabel)
        {
            string ret = email;
            if (null == ret && null != contact)
            {
                ret = contact.EmailAddresses[preferredLabel].Address;
                if (null == ret)
                {
                    ret = contact.EmailAddresses.Default.Address;
                }
            }
            return ret ?? string.Empty;
        }

        internal static string GetCoercedPhone(string phone, Contact contact, string preferredLabel)
        {
            string ret = phone;
            if (null == ret && null != contact)
            {
                ret = contact.PhoneNumbers[preferredLabel].Number;
                if (null == ret)
                {
                    ret = contact.PhoneNumbers.Default.Number;
                }
            }
            return ret ?? string.Empty;
        }

        private static string _ReplaceTokenOnId(string id, string token, string value)
        {
            Assert.IsNotNull(id);

            Dictionary<string, string> idDictionary = ContactUtil.TokenizeId(id.Trim());
            if (idDictionary.ContainsKey(token))
            {
                if (null != value)
                {
                    idDictionary[token] = value;
                }
                else
                {
                    idDictionary.Remove(token);
                }
            }
            else if (null != value)
            {
                idDictionary.Add(token, value);
            }
            return _BuildPersonId(idDictionary);
        }

        internal static string ReplaceEmailOnId(string id, string email)
        {
            return _ReplaceTokenOnId(id, EmailToken, email);
        }

        internal static string ReplacePhoneOnId(string id, string phone)
        {
            return _ReplaceTokenOnId(id, PhoneToken, phone);
        }

        internal static string ReplacePreferredLabelOnId(string id, string label)
        {
            return _ReplaceTokenOnId(id, PreferredLabelToken, label);
        }
        #endregion

        public Person(Contact contact, string name, string phone, string email, string preferredLabel)
        {
            _contact = contact;
            _name = name;
            _phone = phone;
            _email = email;
            _preferredLabel = preferredLabel;
            _id = GenerateId(contact, phone, email, preferredLabel);
        }

        public Person(string name, string id, ContactManager manager)
        {
            _name = name;
            _id = id;

            _contact = null;
            if (null != manager)
            {
                manager.TryGetContact(id, out _contact);
            }

            ParseId(id, out _phone, out _email, out _preferredLabel);
        }

        public Person(string name, string phone, string email, string preferredLabel)
            : this(null, name, phone, email, preferredLabel)
        { }

        public Person(Contact contact)
            : this(contact, null, null, null, null)
        { }

        public Person(string name)
            : this(name, null, null)
        { }

        public string FormattedName
        {
            get { return GetCoercedName(_name, _contact, _preferredLabel); }
        }

        public string ExplicitName
        {
            get { return _name; }
        }

        public string Id
        {
            get { return _id ?? ""; }
        }

        public string Email
        {
            get { return GetCoercedEmail(_email, _contact, _preferredLabel); }
        }

        public string ExplicitEmail
        {
            get { return _email; }
        }

        public string Phone
        {
            get { return GetCoercedPhone(_phone, _contact, _preferredLabel); }
        }

        public string ExplicitPhone
        {
            get { return _phone; }
        }

        public Contact Contact
        {
            get { return _contact; }
        }

        public string PreferredLabel
        {
            get { return _preferredLabel ?? ""; }
        }

        #region Object Overrides

        public override bool Equals(object obj)
        {
            try
            {
                return this.Equals((Person)obj);
            }
            catch (InvalidCastException)
            {
                return false;
            }
        }

        public override int GetHashCode()
        {
            return Utility.GenerateHashCode(this);
        }

        public override string ToString()
        {
            return Utility.GenerateToString(this);
        }

        #endregion

        public bool Equals(Person other, StringComparison comparisonType)
        {
            return string.Equals(this.Id, other.Id, comparisonType)
                && string.Equals(this.FormattedName, other.FormattedName, comparisonType);
        }

        #region IEquatable<Person> Members

        public bool Equals(Person other)
        {
            return Equals(other, StringComparison.InvariantCulture);
        }

        #endregion

        public static bool operator ==(Person left, Person right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(Person left, Person right)
        {
            return !left.Equals(right);
        }
    }

    public class PersonBuilder : INotifyPropertyChanged
    {
        private string _name;
        private string _id;
        private string _email;
        private string _phone;
        private string _preferredLabel;

        private void _OnPropertyChanged(string propertyName)
        {
            Assert.IsFalse(string.IsNullOrEmpty(propertyName));

            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (null != handler)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public PersonBuilder(string name)
            : this(name, null)
        { }

        public PersonBuilder(string name, string id)
        {
            _name = name;
            _id = id;
            Person.ParseId(_id, out _phone, out _email, out _preferredLabel);
        }

        public PersonBuilder(Person person)
            : this(person.FormattedName, person.Id)
        { }

        public PersonBuilder()
            : this(null, null)
        { }

        public string FormattedName
        {
            get { return _name ?? ""; }
            set
            {
                if (_name != value)
                {
                    _name = value;
                    _OnPropertyChanged("FormattedName");
                }
            }
        }

        public string Id
        {
            get { return _id ?? ""; }
            // This replaces all values of phone, email, and preferredLabels.
            set
            {
                if (_id != value)
                {
                    string phone;
                    string email;
                    string label;
                    Person.ParseId(value, out phone, out email, out label);

                    // Let the change notifications for the tokens get fired before applying the Id change.
                    Phone = phone;
                    Email = email;
                    PreferredLabel = label;

                    _id = value;
                    _OnPropertyChanged("Id");
                }
            }
        }

        public string Phone
        {
            get { return _phone; }
            set
            {
                if (_phone != value)
                {
                    _phone = value;
                    _OnPropertyChanged("Phone");

                    Id = Person.ReplacePhoneOnId(Id, value);
                }
            }
        }

        public string Email
        {
            get { return _email; }
            set
            {
                if (_email != value)
                {
                    _email = value;
                    _OnPropertyChanged("Email");

                    Id = Person.ReplaceEmailOnId(Id, value);
                }
            }
        }

        public string PreferredLabel
        {
            get { return _preferredLabel; }
            set
            {
                if (_preferredLabel != value)
                {
                    _preferredLabel = value;
                    _OnPropertyChanged("PreferredLabel");

                    Id = Person.ReplacePreferredLabelOnId(Id, value);
                }
            }
        }

        public Person ToPerson()
        {
            return ToPerson(null);
        }

        public Person ToPerson(ContactManager manager)
        {
            return new Person(FormattedName, Id, manager);
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }
}
